package gameserver.network.game;

import gameserver.model.account.Account;
import gameserver.model.gameobjects.player.Player;
import gameserver.network.Crypt;
import gameserver.network.factories.GamePacketHandlerFactory;
import gameserver.taskmanager.FIFORunnableQueue;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import javolution.util.FastList;

import org.apache.log4j.Logger;

import com.my.commons.network.AConnection;
import com.my.commons.network.Dispatcher;
import com.my.commons.utils.concurrent.RunnableStatsManager;

public class PlayerConnection extends AConnection{
	
	private static Logger log = Logger.getLogger(PlayerConnection.class);
	
	public static enum State
	{
		CONNECTED,
		AUTHED,
		IN_GAME;
	}
	
	private State state;

    private Account account;
    
    private final Crypt crypt = new Crypt();
    
    private Player activePlayer;
    
    private GamePacketHandler gamePacketHandler;
    
    private long lastPingTimeMS;

    private int nbInvalidPackets = 0;
    private final static int MAX_INVALID_PACKETS = 3;
    
    private String macAddress;
	
	private final FastList<GameServerPacket> sendMsgQueue = new FastList<GameServerPacket>();

	public PlayerConnection(SocketChannel sc, Dispatcher d) throws IOException {
		super(sc, d);
		GamePacketHandlerFactory gamePacketHandlerFactory = GamePacketHandlerFactory.getInstance();
		this.gamePacketHandler = gamePacketHandlerFactory.getPacketHandler();
		state = State.CONNECTED;

        String ip = getIP();
        log.debug("connection from: " + ip);

        /** Send SM_KEY packet */
        sendPacket(new SM_KEY());
		// TODO Auto-generated constructor stub
	}
	
	public final int enableCryptKey() {
        return crypt.enableKey();
    }

	@Override
	protected final long getDisconnectionDelay() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	protected void onDisconnect() {
		// TODO Auto-generated method stub
		if (getAccount() != null){
            LoginServer.getInstance().aionClientDisconnected(getAccount().getId());
            
            if(getMacAddress() != null)
        	LoginServer.getInstance().sendBanMacPacket(new SM_MAC(getAccount().getId(), getMacAddress()));

			log.info("[MAC_AUDIT] AccName=" + getAccount().getName() + ",MacAddress=" + getMacAddress() + "");
        }
        if (getActivePlayer() != null) {
            final Player player = getActivePlayer();

            PlayerService.storePlayer(player);
            if (player.getController().isInShutdownProgress())
                PlayerService.playerLoggedOut(player);

                // prevent ctrl+alt+del / close window exploit
            else {
                ThreadPoolManager.getInstance().schedule(new Runnable() {
                    @Override
                    public void run() {
                        PlayerService.playerLoggedOut(player);
                    }
                }, CustomConfig.DISCONNECT_DELAY * 1000);
            }
        }
	}

	@Override
	protected void onServerClose() {
		// TODO Auto-generated method stub
		 close(/* packet, */true);
	}
	
	public final void encrypt(ByteBuffer buf) {
        crypt.encrypt(buf);
    }

	@Override
	protected final boolean processData(ByteBuffer data) {
		// TODO Auto-generated method stub
		try
		{
			if(!crypt.decrypt(data))
			{
				nbInvalidPackets++;
                log.warn("[" + nbInvalidPackets + "/" + MAX_INVALID_PACKETS + "] decrypt fail, skipping client packet...");
                if (nbInvalidPackets >= MAX_INVALID_PACKETS) {
                    log.error("reached MAX_INVALID_PACKETS, closing client connection (wrong client version ?)");
                    return false;
                }
                return true;
			}
		}
		catch (Exception ex) {
            log.error("Exception caught during decrypt: " + ex.getMessage());
            return false;
        }
		
		GameClientPacket pck = gamePacketHandler.handle(data, this);
		if (state == State.IN_GAME && activePlayer == null) {
            log.warn("CHECKPOINT: Skipping packet processing of " + pck.getPacketName() + " for player " );
            return false;
        }
		
		if(pck != null && pck.read())
		{
			getPacketQueue().execute(pck);
		}
		return true;
	}
	
	private FIFORunnableQueue<Runnable> _packetQueue;

    public FIFORunnableQueue<Runnable> getPacketQueue() {
        if (_packetQueue == null)
            _packetQueue = new FIFORunnableQueue<Runnable>() {
            };

        return _packetQueue;
    }

	@Override
	protected final boolean writeData(ByteBuffer data) {
		// TODO Auto-generated method stub
		synchronized (guard) {
            final long begin = System.nanoTime();
            if (sendMsgQueue.isEmpty())
                return false;
            GameServerPacket packet = sendMsgQueue.removeFirst();
            try {
                packet.write(this, data);
                return true;
            }
            finally {
                RunnableStatsManager.handleStats(packet.getClass(), "runImpl()", System.nanoTime() - begin);
            }

        }
	}
	
	public final void sendPacket(GameServerPacket bp) {
        synchronized (guard) {
            /**
             * Connection is already closed or waiting for last (close packet) to be sent
             */
            if (isWriteDisabled())
                return;

            sendMsgQueue.addLast(bp);
            enableWriteInterest();
        }
    }
	
	/*public final void sendPacketSeq(GameServerPacketSeq bps) {
        for (GameServerPacket bp : bps.getPacketSeq())
            sendPacket(bp);
    }*/
	
	public final void close(GameServerPacket closePacket, boolean forced)
	{
		synchronized (guard) {
            if (isWriteDisabled())
                return;

            log.debug("sending packet: " + closePacket + " and closing connection after that.");

            pendingClose = true;
            isForcedClosing = forced;
            sendMsgQueue.clear();
            sendMsgQueue.addLast(closePacket);
            enableWriteInterest();
        }
	}
	
	public final State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public void setMacAddress(String mac) {
        this.macAddress = mac;
    }

    public String getMacAddress() {
        return macAddress;
    }
}
